#================================================
package MSN3::Notification::PP3;
#================================================

use strict;
use warnings;

# For Debugging
use Data::Dumper;

# For Authentication
use LWP::UserAgent;
use Digest::HMAC_SHA1 qw(hmac_sha1);
use XML::LibXML;

use Crypt::CBC;

# General
use MIME::Base64;
use HTML::Entities;

sub checksum { my $o = tell(DATA); seek DATA,0,0; local $/; my $t = unpack("%32C*",<DATA>) % 65535;seek DATA,$o,0; return $t;};

sub new
{
    my $class = shift;
    my ($msn, $handle, $password) = (shift, shift, shift);

    my $self =
    {
        MSN             => $msn,

        Handle          => $handle,
        Password        => $password,

        UserAgent       => new LWP::UserAgent(
        		timeout         => 5,
        		max_redirect    => 0,
        		agent           => 'MSN Explorer/9.0 (MSN 8.0; TmstmpExt)'
        ),

        @_
    };
    bless($self, $class);

    return $self;
}

sub DESTROY
{
    my $self = shift;

    # placeholder for possible destructor code
}

sub AUTOLOAD
{
    my $self = shift;

    $self->error( "method $MSN3::Notification::PP3::AUTOLOAD not defined" ) if( $self->{AutoloadError} );
}

#================================================
# Error / Debugging
#================================================

sub debug
{
    my $self = shift;

    return $self->{MSN}->debug( @_ );
}

sub serverError
{
    my $self = shift;

    return $self->{MSN}->serverError( @_ );
}

sub error
{
    my $self = shift;

    return $self->{MSN}->error( @_ );
}

#================================================
# Passport 3.0 - Authentication
#================================================

sub authenticate
{
    my $self = shift;
    my $ticket = shift || $self->error( "No ticket specified" );
    my $nonce = shift || $self->error( "No nonce specified" );

  	my $body =
  		'<?xml version="1.0" encoding="UTF-8"?>' .
  		'<Envelope xmlns="http://schemas.xmlsoap.org/soap/envelope/" xmlns:wsse="http://schemas.xmlsoap.org/ws/2003/06/secext" xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion" xmlns:wsp="http://schemas.xmlsoap.org/ws/2002/12/policy" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/03/addressing" xmlns:wssc="http://schemas.xmlsoap.org/ws/2004/04/sc" xmlns:wst="http://schemas.xmlsoap.org/ws/2004/04/trust">' .
  			'<Header>' .
  				'<ps:AuthInfo xmlns:ps="http://schemas.microsoft.com/Passport/SoapServices/PPCRL" Id="PPAuthInfo">' .
  					'<ps:HostingApp>{7108E71A-9926-4FCB-BCC9-9A9D3F32E423}</ps:HostingApp>' .
  					'<ps:BinaryVersion>4</ps:BinaryVersion>' .
  					'<ps:UIVersion>1</ps:UIVersion>' .
  					'<ps:Cookies></ps:Cookies>' .
  					'<ps:RequestParams>AQAAAAIAAABsYwQAAAAxMDMz</ps:RequestParams>' .
  				'</ps:AuthInfo>' .
  				'<wsse:Security>' .
  					'<wsse:UsernameToken Id="user">' .
  						'<wsse:Username>' . $self->html_encode($self->{Handle}) . '</wsse:Username>' .
  						'<wsse:Password>' . $self->html_encode($self->{Password}) . '</wsse:Password>' .
  					'</wsse:UsernameToken>' .
  				'</wsse:Security>' .
  			'</Header>' .
  			'<Body>' .
  				'<ps:RequestMultipleSecurityTokens xmlns:ps="http://schemas.microsoft.com/Passport/SoapServices/PPCRL" Id="RSTS">' .
  					'<wst:RequestSecurityToken Id="RST0">' .
  						'<wst:RequestType>http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue</wst:RequestType>' .
  						'<wsp:AppliesTo><wsa:EndpointReference><wsa:Address>http://Passport.NET/tb</wsa:Address></wsa:EndpointReference></wsp:AppliesTo>' .
  					'</wst:RequestSecurityToken>' .
  					'<wst:RequestSecurityToken Id="RST1">' .
  						'<wst:RequestType>http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue</wst:RequestType>' .
  						'<wsp:AppliesTo><wsa:EndpointReference><wsa:Address>messengerclear.live.com</wsa:Address></wsa:EndpointReference></wsp:AppliesTo>' .
  						'<wsse:PolicyReference URI="' . $self->html_encode($ticket) . '"></wsse:PolicyReference>' .
  					'</wst:RequestSecurityToken>' .
  					'<wst:RequestSecurityToken Id="RST2">' .
  						'<wst:RequestType>http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue</wst:RequestType>' .
  						'<wsp:AppliesTo><wsa:EndpointReference><wsa:Address>contacts.msn.com</wsa:Address></wsa:EndpointReference></wsp:AppliesTo>' .
  						'<wsse:PolicyReference URI="MBI"></wsse:PolicyReference>' .
  					'</wst:RequestSecurityToken>' .
  					'<wst:RequestSecurityToken Id="RST3">' .
  						'<wst:RequestType>http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue</wst:RequestType>' .
  						'<wsp:AppliesTo><wsa:EndpointReference><wsa:Address>storage.msn.com</wsa:Address></wsa:EndpointReference></wsp:AppliesTo>' .
  						'<wsse:PolicyReference URI="MBI"></wsse:PolicyReference>' .
  					'</wst:RequestSecurityToken>' .
  					'<wst:RequestSecurityToken Id="RST4">' .
  						'<wst:RequestType>http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue</wst:RequestType>' .
  						'<wsp:AppliesTo><wsa:EndpointReference><wsa:Address>messenger.msn.com</wsa:Address></wsa:EndpointReference></wsp:AppliesTo>' .
  						'<wsse:PolicyReference URI="?id=507"></wsse:PolicyReference>' .
  					'</wst:RequestSecurityToken>' .
  					'<wst:RequestSecurityToken Id="RST5">' .
  						'<wst:RequestType>http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue</wst:RequestType>' .
  						'<wsp:AppliesTo><wsa:EndpointReference><wsa:Address>livecontacts.live.com</wsa:Address></wsa:EndpointReference></wsp:AppliesTo>' .
  						'<wsse:PolicyReference URI="MBI"></wsse:PolicyReference>' .
  					'</wst:RequestSecurityToken>' .
  				'</ps:RequestMultipleSecurityTokens>' .
  			'</Body>' .
  		'</Envelope>';

  	# Do a new POST request then
  	my $req = HTTP::Request->new(POST => "https://login.live.com/RST.srf");
  	   $req->content($body);

  	my $resp = $self->{UserAgent}->request($req);
  	if($resp->is_success)
  	{
    		if(my $dom = $self->{MSN}->getNotification()->{XML}->parse_string($resp->content))
        {
            foreach my $key ($dom->findnodes("//S:Envelope/S:Body/*[local-name()='RequestSecurityTokenResponseCollection']/*[local-name()='RequestSecurityTokenResponse']"))
            {
                my $address = $key->findvalue("./*[local-name()='AppliesTo']/*[local-name()='EndpointReference']/*[local-name()='Address']");
                $self->{MSN}->{Tokens}->{$address}->{Token} = $key->findvalue("./*[local-name()='RequestedSecurityToken']/*[local-name()='BinarySecurityToken']");
                $self->{MSN}->{Tokens}->{$address}->{BinarySecret} = $key->findvalue("./*[local-name()='RequestedProofToken']/*[local-name()='BinarySecret']");
            }

            return ($self->{MSN}->{Tokens}->{'messengerclear.live.com'}->{Token}, $self->encodeToken($self->{MSN}->{Tokens}->{'messengerclear.live.com'}->{BinarySecret}, $nonce));
    		} elsif ($resp->content =~ m!<faultcode>wsse:FailedAuthentication</faultcode>!i) {
            $self->serverError("AUTH ERROR: Login details incorrect.");
      			return undef;
    		} else {
            $self->serverError("AUTH ERROR: ". $resp->{_msg});
      			return undef;
    		}
  	} else {
        $self->serverError("AUTH ERROR: ". $resp->{_msg});
  	}

  	return undef;
}

#================================================
# Encryption functions
#================================================

sub encodeToken
{
  	my $self = shift;
    my $secret = shift;
    my $nonce = shift;

    my $key1 = decode_base64($secret);
    my $key2 = $self->derive_key($key1, "WS-SecureConversationSESSION KEY HASH");
    my $key3 = $self->derive_key($key1, "WS-SecureConversationSESSION KEY ENCRYPTION");

    my $hash = hmac_sha1($nonce, $key2);
    my $iv = chr(0) . chr(0) . chr(0) . chr(0) . chr(0) . chr(0) . chr(0) . chr(0);

    my $cbc = Crypt::CBC->new(
        -literal_key => 1,
        -cipher     => 'DES_EDE3',
        -iv         => $iv,
        -key        => $key3,
        -header     => 'none',
        -padding    => 'null',
    );

    my $des3hash = $cbc->encrypt($nonce.chr(8).chr(8).chr(8).chr(8).chr(8).chr(8).chr(8).chr(8));

    my $line =	chr(28) . chr(0) . chr(0) . chr(0) .
  			chr(1) . chr(0) . chr(0) . chr(0) .
  			chr(3) . chr(102) . chr(0) . chr(0) .
  			chr(4) . chr(128) . chr(0) . chr(0) .
  			chr(8) . chr(0) . chr(0) . chr(0) .
  			chr(20) . chr(0) . chr(0) . chr(0) .
  			chr(72) . chr(0) . chr(0) . chr(0) .
  			$iv . $hash . $des3hash;

    return encode_base64($line, '');
}

sub derive_key
{
    my $self = shift;
    my $key = shift;
    my $magic = shift;

  	my $hash1 = hmac_sha1($magic, $key);
  	my $hash2 = hmac_sha1($hash1.$magic, $key);
  	my $hash3 = hmac_sha1($hash1, $key);
  	my $hash4 = hmac_sha1($hash3.$magic, $key);

    return $hash2 . substr($hash4, 0, 4);
}

sub html_encode
{
    my $self = shift;
    my $string = shift;

    $string =~ s/&/&amp;/g;
    $string =~ s/</&lt;/g;
    $string =~ s/>/&gt;/g;
    $string =~ s/'/&apos;/g;
    $string =~ s/"/&quot;/g;

    return $string;
}

sub html_decode
{
    my $self = shift;
    my $string = shift;

    return decode_entities($string);
}

return 1;
__DATA__